/*
 * Copyright (C) 2021 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package dev.ahamed.mva.sample.view.widget;

import ohos.agp.components.VelocityDetector;
import ohos.multimodalinput.event.TouchEvent;

/**
 * 手势识别器
 *
 * @author 裴云飞
 * @date 2020/12/27
 */
public class GestureDetector {
    /**
     * OnGestureListener
     */
    public interface OnGestureListener {
        /**
         * 发生滚动时回调
         *
         * @param e1 手指按下时的事件
         * @param e2 手指移动时的事件
         * @param distanceX 横向的移动距离
         * @param distanceY 纵向的移动距离
         * @return 回调
         */
        boolean onScroll(TouchEvent e1, TouchEvent e2, float distanceX, float distanceY);
    }

    private int mTouchSlopSquare;
    private final OnGestureListener mListener;
    private boolean mAlwaysInTapRegion;
    private TouchEvent mCurrentDownEvent;
    private float mLastFocusX;
    private float mLastFocusY;
    private float mDownFocusX;
    private float mDownFocusY;
    private VelocityDetector mVelocityTracker;

    /**
     * GestureDetector
     *
     * @param listener 手势监听事件
     */
    public GestureDetector(OnGestureListener listener) {
        mListener = listener;
        int touchSlop = 8;
        // 滑动的时候，手指的移动要大于这个距离才算发生了滚动
        mTouchSlopSquare = touchSlop * touchSlop;
    }

    /**
     * onTouchEvent
     *
     * @param ev TouchEvent对象
     * @return 事件是否销毁
     */
    public boolean onTouchEvent(TouchEvent ev) {
        final int action = ev.getAction();
        if (mVelocityTracker == null) {
            mVelocityTracker = VelocityDetector.obtainInstance();
        }
        mVelocityTracker.addEvent(ev);
        final boolean pointerUp =
                action == TouchEvent.OTHER_POINT_UP;
        final int skipIndex = pointerUp ? ev.getIndex() : -1;
        float sumX = 0;
        float sumY = 0;
        final int count = ev.getPointerCount();
        for (int i = 0; i < count; i++) {
            if (skipIndex == i) {
                continue;
            }
            sumX = (float) ((double) sumX + (double) ev.getPointerPosition(i).getX());
            sumY = (float) ((double) sumY + (double) ev.getPointerPosition(i).getY());
        }
        final int div = pointerUp ? count - 1 : count;
        final float focusX = sumX / div;
        final float focusY = sumY / div;
        boolean handled = false;
        switch (action) {
            case TouchEvent.OTHER_POINT_DOWN:
                mDownFocusX = mLastFocusX = focusX;
                mDownFocusY = mLastFocusY = focusY;
                break;
            case TouchEvent.OTHER_POINT_UP:
                mDownFocusX = mLastFocusX = focusX;
                mDownFocusY = mLastFocusY = focusY;
                break;
            case TouchEvent.PRIMARY_POINT_DOWN:
                mDownFocusX = mLastFocusX = focusX;
                mDownFocusY = mLastFocusY = focusY;
                mCurrentDownEvent = ev;
                mAlwaysInTapRegion = true;
                break;
            case TouchEvent.POINT_MOVE:
                handlePointMove(ev, focusX, focusY);
                break;
            case TouchEvent.PRIMARY_POINT_UP:
            case TouchEvent.CANCEL:
                break;
        }
        return handled;
    }

    private boolean handlePointMove(TouchEvent ev, float focusX, float focusY) {
        boolean handled = false;
        final float scrollX = (float) ((double) mLastFocusX - (double) focusX);
        final float scrollY = (float) ((double) mLastFocusY - (double) focusY);
        if (mAlwaysInTapRegion) {
            final int deltaX = (int) ((double) focusX - (double) mDownFocusX);
            final int deltaY = (int) ((double) focusY - (double) mDownFocusY);
            int distance = (deltaX * deltaX) + (deltaY * deltaY);
            int slopSquare = mTouchSlopSquare;

            if (distance > slopSquare) {
                handled = mListener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY);
                mLastFocusX = focusX;
                mLastFocusY = focusY;
                mAlwaysInTapRegion = false;
            }
        } else if ((Math.abs(scrollX) >= 1) || (Math.abs(scrollY) >= 1)) {
            handled = mListener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY);
            mLastFocusX = focusX;
            mLastFocusY = focusY;
        } else {
            handled = false;
        }
        return handled;
    }
}
